tree grammar Evaluador;

options {
    tokenVocab = AnalisisParser;
  ASTLabelType = MiArbol;
  output = AST;
    backtrack = true;
    rewrite = true;
}

tokens {
  ERROR;
}

@header {
  package practicaspl.doceuno;
    import java.util.Map;
    import java.util.HashMap;
}

@members {
  int historial = 0;
  boolean error = false;
  boolean errorunario=false;
  Map<String, ArrayList<Float>> historialID = new HashMap<String, ArrayList<Float>>();

  /** Points to functions tracked by tree builder. */
    private List<CommonTree> functionDefinitions;


    /** Remember local variables. Currently, this is only the function parameter.
     */
    private final Map<String, Float> localMemory = new HashMap<String, Float>();


    /** Remember global variables set by =. */
    private Map<String, Float> globalMemory = new HashMap<String, Float>();
    
    
    /** Set up an evaluator with a node stream; and a set of function definition ASTs. */
    public Evaluador(CommonTreeNodeStream nodes, List<CommonTree> functionDefinitions) {
        this(nodes);
        this.functionDefinitions = functionDefinitions;
    }



  /** Set up a local evaluator for a nested function call. The evaluator gets the definition
     *  tree of the function; the set of all defined functions (to find locally called ones); a
     *  pointer to the global variable memory; and the value of the function parameter to be
     *  added to the local memory.
     */
    private Evaluador(CommonTree function,
                 List<CommonTree> functionDefinitions,
                 Map<String, Float> globalMemory,
                 Float paramValue) {
        // Expected tree for function: ^(FUNC IDENT ( INT | IDENT ) expr)
        this(new CommonTreeNodeStream(function.getChild(2)), functionDefinitions);
        this.globalMemory = globalMemory;
        localMemory.put(function.getChild(1).getText(), paramValue);
    }

    
     private CommonTree findFunction(String name, Float paramValue) {
        SEARCH:
        for (CommonTree f : functionDefinitions) {
            // Expected tree for f: ^(FUNC IDENT (IDENT | INT) expr)
            if (f.getChild(0).getText().equals(name)) {
                // Check whether parameter matches
                CommonTree formalPar = (CommonTree) f.getChild(1);
                if (formalPar.getToken().getType() == NUMERO
                    && !new Float(formalPar.getToken().getText()).equals(paramValue)) {
                        // Constant in formalPar list does not match actual value -> no match.
                        continue SEARCH;
                }
                // Parameter (value for INT formal arg) as well as fct name agrees!
                return f;
            }
        }
        return null;
    }
    

    /** Get value of name up call stack. */
    public float getValue(String name) {
      
        Float value = localMemory.get(name); 
               
        if (value != null) {
            return value;
        }
        
        value = globalMemory.get(name);
        
        if (value != null) {
            return value;
        }
        
        // not found in local memory or global memory
        System.err.println("Variable " + name + " no definida.");
        return 0;
    }
}


instrucciones 
  : ^(INSTRS tipoentrada*)
  ;

tipoentrada
  : result = expr { System.out.println("Resultado Expresion: " + $result.result); }
  | asignacion
  | ^(FUNC .+)
  ;

asignacion
  : ^(oper=IGUAL id=IDENT exp=expr) { 
      $oper.setResult($exp.result); 
    System.out.println("Resultado Asignacion: " + $id.getText() + " = " + $exp.result);
    
    globalMemory.put($id.getText(), $exp.result);

    ArrayList<Float> lista = new ArrayList<Float>();
    
    if (historialID.containsKey($id.getText())) {
      lista = (ArrayList<Float>) historialID.get($id.getText()).clone(); 
    }
    
    lista.add($exp.result);
    historialID.put($id.getText(),lista); 
  }
  ;
  
parametro returns [float value = 0]
  : n=NUMERO { $parametro.value = Float.parseFloat($n.getText()); }
  | reg=REAL { $parametro.value = Float.parseFloat($reg.getText()); }
  | id=IDENT { 
    if (historial == 0) { 
      $parametro.value = getValue($id.getText());
    } else {
      error = false;
    
      if (historialID.containsKey($id.getText())) {
        ArrayList<Float> histID = new ArrayList<Float>();
        histID = (ArrayList<Float>) historialID.get($id.getText()).clone();
        
        if (histID.size() - 1 < historial) {
          error = true;
        } else {
          int pos = histID.size() - 1 - historial;
        
          Float valor = histID.get(pos);
          $parametro.value = valor;
        }
      } else {
        error = true;
      }
      
      if (error) {    
        errorunario = true;
      
        System.err.println("ERROR. No existe valor anterior para ese identificador");
        $parametro.value = 0;
      }
    } 
  }
  ->{error}? ERROR
  ->IDENT
  ;
  
expr returns [float result = 0] 
  : parametro { $expr.result = $parametro.value; }
  | call      { $expr.result = $call.value; }
  | ^(oper=MAS     izq=expr der=expr) { $expr.result = $izq.result + $der.result; $oper.setResult($result); }
  | ^(oper=MENOS   izq=expr der=expr) { $expr.result = $izq.result - $der.result; $oper.setResult($result); }
  | ^(oper=MULT     izq=expr der=expr) { $expr.result = $izq.result * $der.result; $oper.setResult($result); }
  | ^(oper=DIV izq=expr der=expr) { $expr.result = $izq.result / $der.result; $oper.setResult($result); }
  | ^(oper=MENOS n=NUMERO)            { $expr.result = -Float.parseFloat($n.getText()); $oper.setResult($result); }
  | ^(oper=UNARIO  
        {if (historial == 0)
          errorunario =false;
        historial++; 
      } 
    
    e=expr) { 
    if (error && historial>1) {
      //System.out.println("ERROR en el operador UNARIO");
      Tree defineNode = $oper.getParent();
      Tree stmtNode = defineNode.getParent();
      stmtNode.deleteChild(defineNode.getChildIndex());
    }
    else {
            
      $expr.result = $e.result;
     } 
     historial--;
     
     if (historial == 0)
      errorunario = false;
  }
    ;
  catch [ArithmeticException ae] {
  
  }

call returns [float value]
    : ^(oper=CALL IDENT parametro) { 
      Float p = new Float($parametro.value);
      
    CommonTree funcRoot = findFunction($IDENT.getText(), p);
    
    if (funcRoot == null) {
      System.err.println("No existe la llamada " + $IDENT.getText() + "(" + p + ")");
    } else {
      Evaluador e = new Evaluador(funcRoot, functionDefinitions, globalMemory, p);
      $call.value = e.expr().result;
      $oper.setResult($call.value);
    }
  }
    ;
